package pvm.audiotrackdriver;

import java.io.IOException;
import java.nio.ByteBuffer;
import android.util.Log;
import android.media.AudioTrack;
import android.media.AudioManager;
import android.media.AudioFormat;

public class AudioTrackDriver
{
    private static Object soundLock = new Object();
    int nativeDataPtr = 0;
    boolean exitFlag = false;
    boolean isRunning = false;
    byte fillVal = 0;
    Thread thread = null;
    boolean pauseRendering = false;
    boolean isPaused = false;
    
    int sampleRateInHz = 44100;
    int channelConfig = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
    int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
    int bufferSizeInSamples = (int)(22050 * 2 * 0.2);
    int byteBufferSizeInBytes = bufferSizeInSamples * 2 / 4;
    int sampleSize = 1;
    
    public static AudioTrackDriver createDriver(int param, int sampleRate, int bitsPerSample, boolean stereo, int bufferLengthInMS)
    {
    	if(bitsPerSample!=8 && bitsPerSample!=16)
    		return null; 	

    	AudioTrackDriver driver = new AudioTrackDriver();
    	driver.startSound(param, sampleRate, bitsPerSample, stereo, bufferLengthInMS);
    	return driver;
    }
    
    public void changeBufferLength(int newLengthMS)
    {
        synchronized(soundLock)
        { 	
        	stopSoundThread();
        	startThread(newLengthMS);
        }
    }
    
    public int getCurrentBufferLengthMS()
    {
    	int bufferSizeInMS = bufferSizeInSamples * 1000 / sampleRateInHz;
    	if(channelConfig == AudioFormat.CHANNEL_CONFIGURATION_STEREO)
    		bufferSizeInMS /= 2;
    	return bufferSizeInMS;
    }
    
    public int getCurrentBufferLengthInBytes()
    {
    	return bufferSizeInSamples * sampleSize;
    }
    
    protected void stopSoundThread()
    {
    	if(thread==null)
    		return;
    	if(thread.isAlive()==false)
    		return;
    	
    	exitFlag = true;
    	
    	resumeDevice();
    	try
    	{
    		thread.join(2000);
    	}
    	catch(Exception ex)
    	{
    	}
    	if(thread.isAlive())
    		thread.interrupt();
    	
   		thread = null;
    }
    
    protected void startSound(int param, int sampleRate, int bitsPerSample, boolean stereo, int bufferLengthInMS)
    {
    	if(isRunning)
    		return;
    	
    	nativeDataPtr = param;
    	
    	sampleRateInHz = sampleRate;
    	if(stereo)
    		channelConfig = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
    	else
    		channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;
    		
        audioFormat = (bitsPerSample==16)?AudioFormat.ENCODING_PCM_16BIT:AudioFormat.ENCODING_PCM_8BIT;
    	sampleSize = bitsPerSample >> 3;    
    	
    	startThread(bufferLengthInMS);
    }
    

    protected void startThread(int bufferLengthInMS)
    {
    	exitFlag = false;
    	pauseRendering = false;
    	
    	bufferSizeInSamples = bufferLengthInMS * sampleRateInHz;
    	if(channelConfig == AudioFormat.CHANNEL_CONFIGURATION_STEREO)
    		bufferSizeInSamples *= 2;
    	
    	bufferSizeInSamples /= 1000;
    	
    	int minBufferSize = AudioTrack.getMinBufferSize (sampleRateInHz, channelConfig, audioFormat);
    	//if(getCurrentBufferLengthInBytes() < minBufferSize)
    	{
    		bufferSizeInSamples = minBufferSize / sampleSize;
    	}
    	
    	int stepSizeInSamples = 1;
    	if(channelConfig == AudioFormat.CHANNEL_CONFIGURATION_STEREO)
    		stepSizeInSamples = 2;
    	
    	int bufferSizeInSteps = bufferSizeInSamples / stepSizeInSamples;
    	if((bufferSizeInSteps & 1) != 0)
    		bufferSizeInSteps++;
    	
    	if((bufferSizeInSteps & 2) != 0)
    		bufferSizeInSteps += 2;
    	
    	if((bufferSizeInSteps & 4) != 0)
    		bufferSizeInSteps += 4;
    	
    	bufferSizeInSamples = bufferSizeInSamples * stepSizeInSamples;
    	
    	Runnable runnable = new Runnable()
    	{
			public void run()
			{
				try
				{
					audio_thread();
				}
				catch (IOException e)
				{
					e.printStackTrace();
				}		
			}
		};
    	
    	thread = new Thread(runnable);
    	thread.start();
    }
    
    public void notifyThread()
    {
    	if(thread!=null && thread.isAlive())
    	{
    		synchronized (soundLock)
    		{
	    		try
	    		{
	    			soundLock.notifyAll();
	    		}
	    		catch(Exception e)
	    		{
	    			e.printStackTrace();
	    		}
    		}
    	}
    }
    
    public void suspendDevice()
    {
    	pauseRendering = true;
    }
    
    public void resumeDevice()
    {
    	pauseRendering = false;
    	notifyThread();
    	System.out.println("-----> resumeDevice");
    }
    
    public boolean isDeviceSuspended()
    {
    	return isPaused;
    }
    
    private int sNativeDriveFillBuffer(int param, ByteBuffer buf)
    {
    	int ret = 0;

        synchronized(soundLock)
        { 	
            ret = nativeDriveFillBuffer(param, buf);
        }

        return ret;
    }
        
    public void audio_thread() throws IOException
    {
    	isRunning = true;
    	isPaused = false;
    	
    	byteBufferSizeInBytes = bufferSizeInSamples * sampleSize / 4;
    	ByteBuffer audioBuffer = ByteBuffer.allocateDirect(byteBufferSizeInBytes);
    	
    	byte[] audioData = new byte[byteBufferSizeInBytes];

    	int minBufferSize = AudioTrack.getMinBufferSize (sampleRateInHz, channelConfig, audioFormat);
    	AudioTrack oTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRateInHz,
	    						channelConfig,
	    						audioFormat, 
	    						minBufferSize /*bufferSizeInSamples * sampleSize*/,
								AudioTrack.MODE_STREAM);

	    Log.i("PVM", "start audio (buffer size in bytes - " + byteBufferSizeInBytes + ")");
	 
	    oTrack.play();	    
	    while (!exitFlag)
	    {
	    	if(pauseRendering)
	    	{
	    			synchronized (soundLock)
	    			{
	    				isPaused = true;
	    				try
	    	    		{
	    					oTrack.pause();
	    					soundLock.wait();
	    					oTrack.play();
	    	    		}
	    	    		catch(Exception e)
	    	    		{
	    	    			e.printStackTrace();
	    	    		}
	    	    	}
	    	}
	    	
	    	isPaused = false;
	    	
	    	if(Thread.currentThread().isInterrupted())
	    		break;
	    	
	    	int paintRes = 0;
	    	paintRes = sNativeDriveFillBuffer(nativeDataPtr, audioBuffer);
	    	
	    	if(paintRes!=0)
	    	{
	    		audioBuffer.position(0);
	    		audioBuffer.get(audioData);
	    	}
	    	else
	    	{
	    		for(int i=0; i<audioData.length; i++)
	    			audioData[i] = fillVal;
	    	}

		    // Write the byte array to the track
		    int writeRes = oTrack.write(audioData, 0, paintRes);
		    if(writeRes<0)
		    {
		    	if(writeRes == AudioTrack.ERROR_INVALID_OPERATION)
		    		writeRes = 0;
		    	if(writeRes == AudioTrack.ERROR_BAD_VALUE)
		    		writeRes = 0;
		    }
	    }
	    Log.i("PVM", "stop audio");
	    
	    // Done writting to the track
	    oTrack.stop();
	    oTrack = null;
	    isRunning = false;
	}

    private native int nativeDriveFillBuffer(int param, ByteBuffer buf);
}